﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NFit.Core.Errors;
using NFit.Core.Interfaces;

namespace NFit.Core
{
    /// <summary>
    /// Manage specification execution
    /// </summary>
    public class DefaultSpecificationExecution : ISpecificationExecution
    {
        /// <summary>
        /// The module that execute the fixtures
        /// </summary>
        public IFixtureExecution FixtureExec { get; set; }
        
        /// <summary>
        /// Start the execution
        /// </summary>
        public IExecutionContext Run(SpecificationDefinition CurrentSpecification)
        {
            IExecutionContext res = ServiceLocator.Resolve<IExecutionContext>();
            Run(res, CurrentSpecification);
            return res;
        }

        /// <summary>
        /// Start the execution
        /// </summary>
        public virtual void Run(IExecutionContext Context, SpecificationDefinition CurrentSpecification)
        {
            if (CurrentSpecification.Fixtures.Count > 0)
            {
                Validate(Context, CurrentSpecification);
                RunSpecification(Context, CurrentSpecification);
            }
        }

        /// <summary>
        /// Run the current specification
        /// </summary>
        protected virtual void RunSpecification(IExecutionContext Context, SpecificationDefinition CurrentSpecification)
        {
            CurrentSpecification.Status.StartTime = DateTime.Now;
            ResolveSpecification(Context, CurrentSpecification);
            RunFixtures(Context, CurrentSpecification);
            CurrentSpecification.Status.EndTime = DateTime.Now;
        }

        /// <summary>
        /// Validate the current specification
        /// </summary>
        protected virtual void Validate(IExecutionContext Context, SpecificationDefinition CurrentSpecification)
        {
            if (CurrentSpecification == null)
                throw new ArgumentNullException("Spec is null");
            if (Context == null)
                throw new ArgumentNullException("Execution context is not initialized");           
        }

        /// <summary>
        /// Look for a specification implementation
        /// </summary>
        protected virtual void ResolveSpecification(IExecutionContext Context, SpecificationDefinition CurrentSpecification)
        {
            if (string.IsNullOrEmpty(CurrentSpecification.ClassName))
            {
                if (!string.IsNullOrEmpty(CurrentSpecification.Fixtures[0][0][0].Text))
                {
                    Type t = Context.Catalog.GetFixture(CurrentSpecification.Fixtures[0][0][0].Text);
                    if (t.IsATestImplementation())
                    {
                        CurrentSpecification.ClassType = t;
                        CurrentSpecification.Fixtures.RemoveAt(0);
                    }
                }
            }
            else
            {
                CurrentSpecification.ClassType = Context.Catalog.GetFixture(CurrentSpecification.ClassName);
                if (CurrentSpecification.ClassType == null)
                {
                    CurrentSpecification.Status.Success = false;
                    CurrentSpecification.Status.NestedError = ElementNotFoundError.Specification(CurrentSpecification.ClassName);
                }
                if (!CurrentSpecification.ClassType.IsSubclassOf(typeof(AbstractSpecImplementation)))
                {
                    CurrentSpecification.Status.Success = false;
                    CurrentSpecification.Status.NestedError = ElementNotFoundError.Specification(CurrentSpecification.ClassName);
                    //throw new ArgumentException(string.Format("Test implementation {1} must inherit from {0}", typeof(AbstractSpecImplementation).FullName, Specification.ClassName));
                }
            }

            if (CurrentSpecification.ClassType != null)
            {
                CurrentSpecification.Implementation =
                    (AbstractSpecImplementation)CurrentSpecification.ClassType.Assembly.CreateInstance(CurrentSpecification.ClassType.FullName);
            }
        }

        /// <summary>
        /// Run the current spec's fixtures
        /// </summary>
        protected virtual void RunFixtures(IExecutionContext Context, SpecificationDefinition CurrentSpecification)
        {            
            bool success = true;
            foreach (var fixture in CurrentSpecification.Fixtures)
            {
                success = success && RunFixture(Context, CurrentSpecification, fixture);
            }
            CurrentSpecification.Status.Success = success;
        }

        /// <summary>
        /// Run a fixture
        /// </summary>
        /// <param name="Context"></param>
        /// <param name="CurrentSpecification"></param>
        /// <param name="fixture"></param>
        /// <returns></returns>
        protected virtual bool RunFixture(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition fixture)
        {
            if (FixtureExec == null)
                FixtureExec = ServiceLocator.Resolve<IFixtureExecution>();
            return FixtureExec.Run(Context, CurrentSpecification, fixture);
        }
    }
}
